package com.autumn.scheduler.job;

import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;

import org.quartz.CalendarIntervalTrigger;
import org.quartz.CronTrigger;
import org.quartz.DailyTimeIntervalTrigger;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.SchedulerException;
import org.quartz.SimpleTrigger;
import org.quartz.Trigger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;

import com.autumn.scheduler.api.QueueJobEventData;
import com.autumn.scheduler.api.constant.ApiConstants;
import com.autumn.scheduler.api.constant.ApiConstants.QueueRunState;
import com.autumn.scheduler.domain.entities.AutumnJobLog;
import com.autumn.scheduler.domain.services.IJobService;
import com.autumn.spring.boot.context.AutumnApplicationContext;
import com.autumn.util.ExceptionUtils;
import com.autumn.util.StringUtils;
import com.autumn.util.json.JsonUtils;

/**
 * 作业抽象(Job)
 *
 * @author 杨昌国 2018-03-19 00:05:56
 */
public abstract class AbstractJob implements Job {

    protected Logger log = LoggerFactory.getLogger(this.getClass());

    /**
     * 获取任务数据长整数值
     *
     * @param jobData      JobDataMap
     * @param key          键
     * @param defaultValue 默认值
     * @return
     */
    protected final long getJobDataLong(JobDataMap jobData, String key, long defaultValue) {
        Object value = jobData.get(key);
        if (value == null) {
            return defaultValue;
        } else {
            if (value instanceof Long) {
                return ((Long) value).longValue();
            } else {
                String str = value.toString();
                if (StringUtils.isNullOrBlank(str)) {
                    return defaultValue;
                }
                return Long.parseLong(str.trim());
            }
        }
    }

    /**
     * 获取任务数据整数值
     *
     * @param jobData      JobDataMap
     * @param key          键
     * @param defaultValue 默认值
     * @return
     */
    protected final int getJobDataInt(JobDataMap jobData, String key, int defaultValue) {
        Object value = jobData.get(key);
        if (value == null) {
            return defaultValue;
        } else {
            if (value instanceof Integer) {
                return ((Integer) value).intValue();
            } else {
                String str = value.toString();
                if (StringUtils.isNullOrBlank(str)) {
                    return defaultValue;
                }
                return Integer.parseInt(str.trim());
            }
        }
    }

    /**
     * 获取任务数据字符窜
     *
     * @param jobData      JobDataMap
     * @param key          键
     * @param defaultValue 默认值
     * @return
     */
    protected final String getJobDataString(JobDataMap jobData, String key, String defaultValue) {
        Object value = jobData.get(key);
        if (value == null) {
            return defaultValue;
        } else {
            return value.toString();
        }
    }

    /**
     * @param jobData
     * @return
     */
    private JobExecutionInfo createJobInfo(JobDataMap jobData) {
        String jobFriendlyName = this.getJobDataString(jobData, JobConstants.JobDataKey.JOB_FRIENDLY_NAME, "");
        String queueName = this.getJobDataString(jobData, JobConstants.JobDataKey.JOB_QUEUE_NAME, "");
        long executeNumbers = this.getJobDataLong(jobData, JobConstants.JobDataKey.EXECUTE_NUMBERS, 0L);
        int executeOperation = this.getJobDataInt(jobData, JobConstants.JobDataKey.EXECUTE_OPERATION,
                ApiConstants.ExecuteOperation.NONE);
        long runNumbers = this.getJobDataLong(jobData, JobConstants.JobDataKey.RUN_NUMBERS, 0L) + 1L;
        String parameterStr = this.getJobDataString(jobData, JobConstants.JobDataKey.EXECUTE_PARAMETER_MAP, null);
        Map<String, Object> par;
        if (StringUtils.isNullOrBlank(parameterStr)) {
            par = new LinkedHashMap<>();
        } else {
            par = JsonUtils.parseMap(parameterStr);
        }
        return new JobExecutionInfo(jobFriendlyName, queueName, executeNumbers, executeOperation, runNumbers, par);
    }

    private String getStringValue(String value) {
        if (value == null) {
            return "";
        }
        return value;
    }

    private String getTriggerType(Trigger trigger) {
        if (CronTrigger.class.isAssignableFrom(trigger.getClass())) {
            return org.quartz.impl.jdbcjobstore.Constants.TTYPE_CRON;
        }
        if (SimpleTrigger.class.isAssignableFrom(trigger.getClass())) {
            return org.quartz.impl.jdbcjobstore.Constants.TTYPE_SIMPLE;
        }
        if (CalendarIntervalTrigger.class.isAssignableFrom(trigger.getClass())) {
            return org.quartz.impl.jdbcjobstore.Constants.TTYPE_CAL_INT;
        }
        if (DailyTimeIntervalTrigger.class.isAssignableFrom(trigger.getClass())) {
            return org.quartz.impl.jdbcjobstore.Constants.TTYPE_DAILY_TIME_INT;
        }
        return "UNKNOWN";
    }

    private AutumnJobLog createJobLog(JobExecutionContext context, JobExecutionInfo jobInfo) {
        AutumnJobLog jobLog = new AutumnJobLog();
        jobLog.setJobExecuteId(jobInfo.getJobExecuteId());
        try {
            jobLog.setSchedName(getStringValue(context.getScheduler().getSchedulerName()));
        } catch (SchedulerException e) {
            jobLog.setSchedName(JobConstants.AUTUMN_QUARTZ_SCHEDULER);
        }
        jobLog.setJobFriendlyName(getStringValue(jobInfo.getJobFriendlyName()));
        jobLog.setJobName(getStringValue(context.getJobDetail().getKey().getName()));
        jobLog.setJobGroup(getStringValue(context.getJobDetail().getKey().getGroup()));
        jobLog.setJobQueueName(QueueJobEventData.JOB_QUEUE_PREFIX + getStringValue(jobInfo.getQueueName()));
        jobLog.setJobClassName(getStringValue(context.getJobInstance().getClass().getName()));
        jobLog.setRunTime(new Date());
        jobLog.setRunException("");
        jobLog.setSchedTime(context.getFireTime());
        jobLog.setSchedOperation(jobInfo.getExecuteOperation());
        try {
            jobLog.setSchedInstanceId(getStringValue(context.getScheduler().getSchedulerInstanceId()));
        } catch (SchedulerException e) {
            jobLog.setSchedInstanceId(context.getFireInstanceId());
        }
        jobLog.setRunOrder(jobInfo.getRunNumbers());
        jobLog.setJobDescription(getStringValue(context.getJobDetail().getDescription()));
        if (jobInfo.getParameterMap() != null && jobInfo.getParameterMap().size() > 0) {
            jobLog.setRunParameter(JsonUtils.toJSONString(jobInfo.getParameterMap()));
        } else {
            jobLog.setRunParameter("");
        }
        jobLog.setTriggerType(this.getTriggerType(context.getTrigger()));
        jobLog.setTriggerDescription(getStringValue(context.getTrigger().getDescription()));
        jobLog.setJobQueueBeginTime(null);
        jobLog.setJobQueueEndTime(null);
        jobLog.setGmtCreate(new Date());
        return jobLog;
    }

    @Override
    public final void execute(JobExecutionContext context) throws JobExecutionException {
        JobDataMap jobData = context.getJobDetail().getJobDataMap();
        JobExecutionInfo jobInfo = createJobInfo(jobData);
        AutumnJobLog jobLog = createJobLog(context, jobInfo);
        IJobService jobService = this.getBean(IJobService.class);
        boolean isOk = false;
        try {
            jobLog.setJobQueueRunState(QueueRunState.SEND_SUCCESS);
            // jobLog.setJobQueueRunState(this.executeInternal(context, jobInfo, jobLog));
            jobLog.setJobQueueRunMessage(QueueRunState.getMessage(jobLog.getJobQueueRunState()));
            jobLog.setRunState(ApiConstants.RunState.SUCCESS);
            isOk = true;
        } catch (Exception e) {
            jobLog.setJobQueueRunState(QueueRunState.SEND_FAIL);
            jobLog.setJobQueueRunMessage(this.getStringValue(e.getMessage()));
            log.error(e.getMessage(), e);
            jobLog.setRunState(ApiConstants.RunState.FAIL);
            jobLog.setRunException(this.getStringValue(e.getMessage() + ":" + ExceptionUtils.toStackTraceString(e)));
        } finally {
            try {
                jobService.runJob(jobLog, jobInfo.isDeleteJob());
                jobData.put(JobConstants.JobDataKey.RUN_NUMBERS, jobInfo.getRunNumbers());
                if (isOk && jobInfo.isPauseJob()) {
                    context.getScheduler().pauseJob(context.getJobDetail().getKey());
                }
                // 改为后面
                this.executeInternal(context, jobInfo, jobLog);
            } catch (Exception e2) {
                log.error(e2.getMessage(), e2);
            }
        }
    }

    /**
     * 内部执行
     *
     * @param context 上下文
     * @param jobInfo 作业信息
     * @throws JobExecutionException
     */
    protected abstract int executeInternal(JobExecutionContext context, JobExecutionInfo jobInfo, AutumnJobLog jobLog)
            throws JobExecutionException;

    /**
     * 获取应用上下文
     *
     * @return
     */
    public final ApplicationContext getAppContext() {
        return AutumnApplicationContext.getContext();
    }

    /**
     * 获取 Bean
     *
     * @param name         名称
     * @param requiredType 类型
     * @return
     * @throws BeansException
     */
    public final <T> T getBean(String name, Class<T> requiredType) throws BeansException {
        return getAppContext().getBean(name, requiredType);
    }

    /**
     * 获取 Bean
     *
     * @param requiredType 类型
     * @return
     * @throws BeansException
     */
    public final <T> T getBean(Class<T> requiredType) throws BeansException {
        return getAppContext().getBean(requiredType);
    }
}
